﻿using System.Collections.Concurrent;
using System.Runtime.CompilerServices;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace zijian666.DI;

/// <summary>
/// 用于获取 <seealso cref="ILogger"/> 前的日志缓存
/// </summary>
/// <param name="capacity"></param>
public class PreLogger : ILogger
{
    private static readonly ConcurrentBag<PreLogger> ALL = [];

    public static PreLogger[] GetAll() => ALL.ToArray();

    private readonly Queue<LogArgs> _logs;
    private readonly int _capacity;

    public PreLogger(int capacity)
    {
        _capacity = capacity;
        _logs = new(capacity);
        ALL.Add(this);
    }

    private ILogger? _to = null;

    [MethodImpl(MethodImplOptions.Synchronized)]
    void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if (_to is not null)
        {
            _to.Log(logLevel, eventId, state, exception, formatter);
            return;
        }
        if (_logs.Count >= _capacity)
        {
            _logs.Dequeue();
        }
        _logs.Enqueue(new(logLevel, eventId, state, exception, formatter(state, exception)));
    }

    [MethodImpl(MethodImplOptions.Synchronized)]
    bool ILogger.IsEnabled(LogLevel logLevel)
    {
        if (_to is not null)
        {
            return _to.IsEnabled(logLevel);
        }
        return true;
    }

    [MethodImpl(MethodImplOptions.Synchronized)]
    IDisposable ILogger.BeginScope<TState>(TState state)
    {
        if (_to is not null)
        {
            return _to.BeginScope(state);
        }
        if (_logs.Count >= _capacity)
        {
            _logs.Dequeue();
        }
        _logs.Enqueue(new(LogLevel.None, new(), state, null, "BeginScope"));
        return new Scope<TState>(this, state);
    }

    /// <summary>
    /// 将缓存的日志写入到指定的 <seealso cref="ILogger"/> 
    /// </summary>
    [MethodImpl(MethodImplOptions.Synchronized)]
    public T WirteTo<T>(T logger)
        where T : ILogger
    {
        while (_logs.TryDequeue(out var log))
        {
            logger.Log(log.LogLevel, log.EventId, log.State, log.Exception, (s, e) => log.Message);
        }
        return logger;
    }

    /// <summary>
    /// 重定向日志
    /// </summary>
    /// <param name="target">目标日志记录器</param>
    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Redrect(ILogger target)
    {
        if (ReferenceEquals(target, this))
        {
            throw new ArgumentException("不能将自己作为目标");
        }
        _to = target;
        if (_logs.Count > 0)
        {
            WirteTo(target);
            _logs.Clear();
            _logs.TrimExcess();
        }
    }

    [MethodImpl(MethodImplOptions.Synchronized)]
    public bool TryRedrect(ILogger target)
    {
        if (_to is not null)
        {
            return false;
        }
        Redrect(target);
        return true;
    }

    [MethodImpl(MethodImplOptions.Synchronized)]
    public bool TryRedrect(IServiceProvider services, string categoryName)
    {
        if (_to is not null)
        {
            return false;
        }
        var logger = services.GetService<ILoggerFactory>()?.CreateLogger(categoryName);
        if (logger is null)
        {
            return false;
        }
        Redrect(logger);
        return true;
    }

    record struct LogArgs(LogLevel LogLevel, EventId EventId, object? State, Exception? Exception, string Message) { }
    class Scope<TState>(PreLogger logger, TState? state) : IDisposable
    {
        private bool _disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!_disposedValue)
            {
                if (disposing)
                {
                    logger._logs.Enqueue(new(LogLevel.None, new(), state, null, "DisposeScope"));
                }
                _disposedValue = true;
            }
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }
}
